# -*- coding: utf-8 -*-
from __future__ import absolute_import

import datetime
import json
import logging.config
import os
import sys
import time

import pymongo
import requests
from celery import Celery
from celery import platforms
from celery.schedules import crontab
from decimal import Decimal
from requests.adapters import HTTPAdapter
from sqlalchemy import func, or_, and_

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if BASE_DIR not in sys.path:
    sys.path.append(BASE_DIR)

from base.connection import redis_util, mongodb_util, get_db_session
from common.definition import OrderStatus, PayType, OrderType, PayStatus, AgencyType
from common.statistics import Statistics
from conf.config import (
    DOMAIN,
    FACTORY_ID,
    DEFAULT_AGENCY_UID,
    DEFAULT_AGENCY_ID,
    REDIS_PWD,
    REDIS_HOST,
    REDIS_PORT,
    REDIS_DB,
    FACTORY_UID,
    IS_CALCULATE_BALANCE_BENEFIT,
    TI_YAN_QUOTA,
    UPDATE_USER_INFO_START_TIME,
    GIFT_ORDER_REFUND_TIMEOUT,
)
from conf.logging import LOGGING_DICT_CONFIG
from daemon import tasks
from helper.mp_helper import fetch_simple_access_token
from helper.payment import pay_util
from helper.payment.pay_util import (
    calculate_pre_benefit,
    BenefitHelper,
    expedite_order_notice,
)
from helper.user import UserHelper
from helper.template_messages import (
    daily_benefit_notice,
    goods_shipped_notice,
    pay_success_notice,
    cancel_order_notice,
)
from helper.coupon_helper import CouponHandler
from helper.payment.refund import BalanceRefund, order_refund, WechatRefund
from util.date_util import convert_ts_2_date
from util.str_util import replace_emoji


logger = logging.getLogger("daemon")
logging.config.dictConfig(LOGGING_DICT_CONFIG)
logging.getLogger("requests").setLevel(logging.ERROR)

broker_url = "redis://:{pwd}@{host}:{port}/{db}".format(
    pwd=REDIS_PWD, host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB
)
app = Celery("scheduler", broker=broker_url, include=["daemon.scheduler"])
platforms.C_FORCE_ROOT = True
schedule = {
    # "get_user_info": {
    #     "task": "daemon.scheduler.get_user_info",  # 指定要调用哪个方法
    #     "schedule": datetime.timedelta(
    #         seconds=90
    #     ),  # 定时任务时间间隔,可以以cron方式运行  "schedule":crontab(minute="*/1"),
    #     "args": (35,),  # 超时时间
    # },
    "update_user_agency": {
        "task": "daemon.scheduler.update_user_agency",
        "schedule": datetime.timedelta(seconds=60),
        "args": (30,),
    },
    # "update_user_info": {
    #     "task": "daemon.scheduler.update_user_info",
    #     "schedule": datetime.timedelta(seconds=1800),
    #     "args": (600,),
    # },
    "calculate_finance_daily_stats": {
        "task": "daemon.scheduler.calculate_finance_daily_stats",
        "schedule": crontab(hour="4", minute="30"),  # 每天凌晨4点半执行
        "args": (600,),
    },
    "delete_expire_order": {
        "task": "daemon.scheduler.delete_expire_order",
        "schedule": crontab(minute=0, hour="*/1"),  # 删除24小时未付款订单每小时执行一次
        "args": (60,),
    },
    "express_notify": {
        "task": "daemon.scheduler.express_notify",
        "schedule": crontab(minute=0, hour="*/1"),  # 发送快递通知每小时执行一次
        "args": (60,),
    },
    "gift_order_refund": {
        "task": "daemon.scheduler.gift_order_refund",
        "schedule": crontab(minute=0, hour="*/1"),  # 礼品订单自动退款每小时执行一次
        "args": (600,),
    },
    "check_calculate_benefit": {
        "task": "daemon.scheduler.check_calculate_benefit",
        "schedule": crontab(minute=0, hour="*/4"),  # 检查收益订单创建是否遗漏每4小时执行一次
        "args": (60,),
    },
    # "partner_daily_benefit": {
    #     "task": "daemon.scheduler.partner_daily_benefit",
    #     "schedule": crontab(hour="12", minute="0"),  # 每天12:00执行
    #     "args": (600,)
    # },
    # "check_daily_benefit": {
    #     "task": "daemon.scheduler.check_daily_benefit",
    #     "schedule": crontab(hour="14,16", minute="0"),  # 检查众筹收益是否遗漏，每天14:00, 16:00各执行一次
    #     "args": (600,)
    # },
    "agency_auto_upgrade": {
        "task": "daemon.scheduler.agency_auto_upgrade",
        "schedule": crontab(minute=0, hour="*/6"),  # 检查合伙人升级每6小时执行一次
        "args": (600,),
    },
    "handle_coupons": {
        "task": "daemon.scheduler.handle_coupons",
        "schedule": crontab(hour="0,12", minute="0"),  # 处理优惠券生效过期每天0点、12点各执行一次
        "args": (300,),
    },
    "check_tiyan_order": {
        "task": "daemon.scheduler.check_tiyan_order",
        "schedule": crontab(hour="0,12", minute="0"),  # 检查体验订单是否过期每天0点、12点各执行一次
        "args": (300,),
    },
    "check_order" : {
        "task": "daemon.scheduler.check_order",
        "schedule": crontab(hour="9,14", minute="0"),  # 检查待发货订单是否超过24小时。每天9点、14点各执行一次
        "args": (300,),
    }
}
app.conf.update(
    beat_schedule=schedule,
    timezone="Asia/Shanghai",  # 设置时区为中国时区，默认是UTC的
    broker_connection_max_retries=0,  # 重连次数，默认100，设为0时表示不停重连
    broker_pool_limit=0,  # 连接broker时的最大连接数，默认10个，设为0表示不限制
)


def is_exists(users, open_id):
    for u in users:
        if u[0] == open_id:
            return True


def _get_user_info(access_token, open_id):
    """
        {
    "subscribe": 1,
    "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
    "nickname": "Band",
    "sex": 1,
    "language": "zh_CN",
    "city": "广州",
    "province": "广东",
    "country": "中国",
    "headimgurl":  "http://wx.qlogo.cn/mmopen/g3
    MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4
    eMsv84eavHiaiceqxibJxCfHe/0",
    "subscribe_time": 1382694957,
    "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
    "remark": "",
    "groupid": 0,
    "tagid_list":[128,2]
    }

        :param access_token:
        :param open_id:
        :return:
    """
    # refer: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
    # https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
    # url = "https://api.weixin.qq.com/sns/userinfo"  # 需要网页授权access_token!

    url = "https://api.weixin.qq.com/cgi-bin/user/info"
    params = dict(
        access_token=access_token,
        openid=open_id,
        lang="zh_CN",
    )
    s = requests.session()
    # 设置重试次数、连接池的连接数量
    s.mount(
        "http://", HTTPAdapter(max_retries=3, pool_maxsize=2, pool_connections=1000)
    )
    s.mount(
        "https://", HTTPAdapter(max_retries=3, pool_maxsize=2, pool_connections=1000)
    )
    response = s.get(url=url, params=params, timeout=5)
    logger.debug(response.text)
    return response.json()


def daily_benefit_handle(order_id, agency_uid, fee, order_fee, open_id, first, type=3):
    class Benefit(object):
        pass

    session = get_db_session([{"db_table": "sdet_benefit", "model": Benefit}])
    now = datetime.datetime.now()
    try:
        bf = Benefit()
        bf.order_id = order_id
        bf.factory_uid = FACTORY_UID
        bf.agency_uid = agency_uid
        bf.type = type
        bf.fee = fee
        bf.order_fee = order_fee
        bf.rate = 0.00
        bf.status = 1
        bf.is_valid = 1
        bf.is_finish = 1
        bf.remarks = "合伙人收益返还"
        bf.update_time = now
        bf.create_time = now
        session.add(bf)
        session.commit()
        logger.info(
            "partner_day_benefit:success, oid=%s, uid=%s, order_id=%s"
            % (open_id, agency_uid, order_id)
        )
    except Exception as e:
        logger.exception(e)
        logger.error(
            "partner_day_benefit:fail, oid=%s, uid=%s, order_id=%s"
            % (open_id, agency_uid, order_id)
        )
    else:
        daily_benefit_notice(fee, open_id, order_id, first)
    finally:
        session.close()


def recommend_daily_benefit(order_id, fee, order_fee, open_id):
    """
    众筹推荐人奖励, 充值天数: 365×2
    :param order_id:
    :param fee:
    :param order_fee:
    :param open_id:
    :return:
    """
    try:

        class Benefit(object):
            pass

        session = get_db_session([{"db_table": "sdet_benefit", "model": Benefit}])

        days = 365 * 2  # 充值天数

        try:
            db = mongodb_util.db
            result = db.sdec_recommender.find_one({"order_id": order_id})
            if result:
                inviter_oid = result.get("inviter_oid", None)
                inviter_uid = UserHelper.get_agency_uid(inviter_oid)
                if not inviter_uid:
                    logger.error(
                        "inviter_uid为空: order_id=%s, inviter_oid=%s"
                        % (order_id, inviter_oid)
                    )
                    return
            else:
                logger.error("查询数据为空: order_id=%s, oid=%s" % (order_id, open_id))
                return
        except Exception as e:
            logger.error("查询数据出错: order_id=%s, oid=%s, e=%s" % (order_id, open_id, e))
            return

        _filter = dict(order_id=order_id, agency_uid=inviter_uid, fee=fee, type=4)
        benefit_times = (
            session.query(func.count(Benefit.id)).filter_by(**_filter).scalar()
        )
        total_benefit = (
            session.query(func.sum(Benefit.fee)).filter_by(**_filter).scalar()
        )
        if not total_benefit:
            total_benefit = Decimal("0")
        total_fee = days * fee
        day_now = time.localtime()
        year = day_now.tm_year
        month = day_now.tm_mon
        day = day_now.tm_mday
        start_time = datetime.datetime(year, month, day, 0, 0, 0)
        end_time = datetime.datetime(year, month, day, 23, 59, 59)
        old_bf = (
            session.query(Benefit)
            .filter_by(**_filter)
            .filter(Benefit.create_time >= start_time)
            .filter(Benefit.create_time <= end_time)
            .first()
        )

        if old_bf:
            logger.error(
                "今日收益已返还:order_id=%s, inviter_oid=%s" % (order_id, inviter_oid)
            )
            return

        if benefit_times >= days or total_benefit >= total_fee:
            logger.error("收益充值次数已满:order_id=%s, oid=%s" % (order_id, open_id))
            return

        logger.info(
            "返还收益: order_id=%s, inviter_oid=%s, inviter_uid=%s"
            % (order_id, inviter_oid, inviter_uid)
        )
        first = "您的托管经营推荐人今日奖励已到账！"
        daily_benefit_handle(
            order_id, inviter_uid, fee, order_fee, inviter_oid, first, type=4
        )
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()


def get_agency_under_nums(num_list, agency_id):
    """
    递归统计合伙人名下高级酒庄代表人数（包括直属和非直属）
    :param num_list:
    :param agency_id:
    :return:
    """

    class Agency(object):
        pass

    session = get_db_session([{"db_table": "ebt_agency", "model": Agency}])
    filter = dict(ebf_agency_parent_id=agency_id, ebf_agency_type=1)
    num = session.query(func.count(Agency.ebf_agency_id)).filter_by(**filter).scalar()
    if num > 0:
        num_list.append(num)

    if num == 0:
        pass
    else:
        agencys = session.query(Agency).filter_by(**filter).all()
        for agency in agencys:
            agency_id = agency.ebf_agency_id
            get_agency_under_nums(num_list, agency_id)

    return num_list


@app.task(soft_time_limit=59, time_limit=60)
def get_user_info(timeout):
    logger.info("执行定时任务 get_user_info")
    try:
        # 从缓存中获取next_openid
        redis = redis_util.client
        next_openid = redis.get("style_next_openid")
        next_openid = next_openid.decode() if next_openid else ""
        logger.debug(f"cache next_openid:{next_openid}")

        class User(object):
            pass

        class UserInfo(object):
            pass

        session = get_db_session(
            [
                {"db_table": "ebt_user", "model": User},
                {"db_table": "ebt_user_info", "model": UserInfo},
            ]
        )

        access_token = fetch_simple_access_token()

        url = "https://api.weixin.qq.com/cgi-bin/user/get"
        params = dict(
            access_token=access_token,
            next_openid=next_openid,
        )
        r = requests.get(url=url, params=params, timeout=30)
        logger.debug(r.text)
        response = r.json()
        next_openid = response.get("next_openid")
        r_total = response.get("total")
        # r_count = response.get("count")
        logger.debug(f"result next_openid:{next_openid}")
        if not next_openid:
            logger.info("没有更多用户数据了")

        if not response.get("data"):
            return
        open_ids = response.get("data").get("openid")
        # 取出数据库中的所有openid
        users = (
            session.query(User)
            .filter_by(ebf_user_factory_id=FACTORY_ID, ebf_user_type=0)
            .with_entities(User.ebf_user_id)
            .all()
        )

        for open_id in open_ids:
            if is_exists(users, open_id):
                continue

            try:
                user_dict = _get_user_info(access_token, open_id)
                user = User()
                user.ebf_user_id = open_id
                if user_dict["unionid"]:
                    user.ebf_user_account = user_dict["unionid"]
                user.ebf_user_password = ""
                user.ebf_user_headurl = user_dict["headimgurl"]

                user.ebf_user_nickname = replace_emoji(user_dict["nickname"])
                user.ebf_user_gender = user_dict["sex"]
                user.ebf_user_type = 0
                user.ebf_user_account_type = 2

                delta = datetime.timedelta(hours=8)
                user.ebf_user_subscribe_date = (
                    convert_ts_2_date(user_dict["subscribe_time"]) - delta
                )
                user.ebf_user_factory_id = FACTORY_ID
                user.ebf_user_agency_uid = DEFAULT_AGENCY_UID
                session.add(user)
                # session.flush()

                user_info = UserInfo()
                user_info.ebf_user_id = user.ebf_user_id
                user_info.ebf_ui_province = user_dict["province"]
                user_info.ebf_ui_city = user_dict["city"]
                user_info.ebf_ui_city = user_dict["city"]
                session.add(user_info)
                # session.flush()
                session.commit()
            except Exception as e:
                logger.exception(e)
        session.close()

        # 每次最多返回10000个，更多时就需要分页了
        if next_openid and r_total > 10000:
            redis.setex("style_next_openid", 3600, next_openid)
    except Exception as e:
        logger.exception(e)


@app.task(soft_time_limit=59, time_limit=60)
def update_user_agency(timeout):
    logger.info("执行定时任务 update_user_agency")
    try:
        # 取出上一次查询的记录的最后的时间
        redis = redis_util.client
        last_bind_time = redis.get("style_device_bind_time")
        if not last_bind_time:
            date_start = datetime.datetime.utcnow() - datetime.timedelta(days=180)
        else:
            date_str = last_bind_time.decode()
            date_struct_time = time.strptime(date_str, "%Y-%m-%d %H:%M:%S")
            date_start = datetime.datetime.fromtimestamp(time.mktime(date_struct_time))
        logger.debug("cache date_start:%s" % date_start)

        # 获取用户
        db = mongodb_util.db
        _filter = {"device_type": 15, "updatetime": {"$gte": date_start}}
        devices = db.devices_users.find(_filter).sort("updatetime", pymongo.ASCENDING)

        class User(object):
            pass

        class Device(object):
            pass

        class Agency(object):
            pass

        session = get_db_session(
            [
                {"db_table": "ebt_user", "model": User},
                {"db_table": "ebt_device", "model": Device},
                {"db_table": "ebt_agency", "model": Agency},
            ]
        )
        last_device = None
        for d in devices:
            last_device = d
            logger.debug(last_device)
            # 检查这个用户是否绑定过合伙人，是否绑定的是默认合伙人
            users = (
                session.query(User)
                .filter_by(
                    ebf_user_factory_id=FACTORY_ID,
                    ebf_user_id=last_device.get("openid"),
                )
                .all()
            )
            if len(users) == 0:
                continue
            user = users[0]
            # 如果存在这个用户，并且用户所属的是默认合伙人，则检查当前绑定的设备的合伙人
            # 以第一次绑定为准，以后再扫描其它合伙人的二维码就无效了
            if user.ebf_user_agency_uid != DEFAULT_AGENCY_UID:
                logger.debug("该用户已绑定合伙人：%s" % user.ebf_user_id)
                continue

            devices = (
                session.query(Device)
                .filter_by(
                    ebf_factory_id=FACTORY_ID, ebf_device_id=last_device.get("did")
                )
                .all()
            )
            if len(devices) == 0:
                logger.debug("设备不属于该思达尔：%s" % last_device.get("did"))
                continue

            device = devices[0]
            # 如果这个设备的所属合伙人为非默认合伙人，就将这个用户的合伙人信息修改为跟设备一样的
            if device.ebf_agency_id == DEFAULT_AGENCY_ID:
                logger.debug("该设备为默认合伙人的：%s" % device.ebf_device_id)
                continue

            agencys = (
                session.query(Agency)
                .filter_by(
                    ebf_factory_id=FACTORY_ID, ebf_agency_id=device.ebf_agency_id
                )
                .all()
            )
            if len(agencys) == 0:
                continue
            agency = agencys[0]
            user.ebf_user_agency_uid = agency.ebf_agency_uid
            logger.debug("更新合伙人信息完成:%s" % last_device.get("openid"))
            # session.flush()
            session.commit()
        session.close()
        if last_device:
            redis.set(
                "style_device_bind_time",
                last_device.get("updatetime").strftime("%Y-%m-%d %H:%M:%S"),
            )
        logger.info("update_user_agency: OK")

    except Exception as e:
        logger.exception(e)


@app.task(soft_time_limit=1200, time_limit=1800)
def update_user_info(timeout):  # sourcery skip: avoid-builtin-shadow
    logger.info("执行定时任务 update_user_info")
    step = 1000  # 每次更新的用户数量

    try:


        class User(object):
            pass

        session = get_db_session(
            [
                {"db_table": "ebt_user", "model": User},
            ]
        )

        redis = redis_util.client
        start_time = redis.get(UPDATE_USER_INFO_START_TIME)
        start_time = start_time.decode() if start_time else datetime.datetime(2017, 1, 1)

        access_token = fetch_simple_access_token()

        # 取出数据库中的所有openid
        if DOMAIN == "wx.laez.com.cn":
            filter = User.ebf_user_id.startswith("ok")
        elif DOMAIN == "wx.style999.com":
            filter = User.ebf_user_id.startswith("o7")
        else:
            filter = User.ebf_user_id.startswith("oo")

        users = (
            session.query(User)
            .filter_by(
                ebf_user_factory_id=FACTORY_ID,
                ebf_user_type=0,
            )
            .filter(filter)
            .filter(
                User.ebf_user_create_date > start_time,
            )
            .order_by(
                User.ebf_user_create_date.asc(),
            )
            .limit(step)
            .all()
        )

        for user in users:
            openid = user.ebf_user_id
            if DOMAIN == "wx.laez.com.cn" and not openid.startswith("ok"):
                continue
            elif DOMAIN == "wx.style999.com" and not openid.startswith("o7"):
                continue
            elif DOMAIN == "wx.laez9.com" and not openid.startswith("oo"):
                continue

            try:
                user_dict = _get_user_info(access_token, openid)
                if "errcode" in user_dict:
                    logger.error(
                        f"update_user_info: 获取用户信息出错 open_id={openid} user_dict={user_dict}"
                    )
                    # 处理令牌无效
                    if user_dict.get("errcode", 0) == 40001:
                        access_token = fetch_simple_access_token(reset=True)
                        user_dict = _get_user_info(access_token, openid)
                if "errcode" in user_dict:
                    logger.error(
                        f"update_user_info: 获取用户信息出错 open_id={openid} user_dict={user_dict}"
                    )
                    continue

                if user_dict.get("subscribe", 0) != 1:
                    # logger.info("update_user_info: 仍未关注 openid=%s" % openid)
                    user.ebf_user_is_subscribe = 0
                    session.commit()
                    continue

                if user_dict["unionid"]:
                    user.ebf_user_account = user_dict["unionid"]
                user.ebf_user_is_subscribe = 1
                user.ebf_user_headurl = user_dict.get("headimgurl")
                nickname = replace_emoji(user_dict.get("nickname", "未关注"))
                user.ebf_user_nickname = nickname[:32] if len(nickname) > 32 else nickname
                user.ebf_user_gender = user_dict.get("sex", 0)
                user.ebf_user_create_date = convert_ts_2_date(
                    user_dict.get("subscribe_time", str(time.time()).split(".")[0])
                ) - datetime.timedelta(hours=8)
                # logger.info(f"update_user_info: success open_id={openid}")
                session.commit()
            except Exception as e:
                logger.exception(f"update_user_info: fail e={e} open_id={openid}")
        # 设置最后一个用户的创建时间
        try:
            if len(users) < step:
                start_time = datetime.datetime(2017, 1, 1)
            else:
                start_time = user.ebf_user_create_date
        except Exception as e:
            logger.error(f"update_user_info: e={e}")
            start_time = datetime.datetime(2017, 1, 1)
        redis.set(UPDATE_USER_INFO_START_TIME, start_time.strftime("%Y-%m-%d %H:%M:%S"))

        # session.flush()
        session.commit()
        session.close()
        logger.info("update_user_info: OK")
    except Exception as e:
        logger.exception(e)


@app.task(soft_time_limit=59, time_limit=60)
def calculate_finance_daily_stats(timeout):
    """
    计算每日汇总
    :param timeout:
    :return:
    """
    logger.info("执行定时任务 calculate_finance_daily_stats")
    try:
        # 默认计算前一天，允许计算指定时间的汇总
        last_date = datetime.datetime.now() - datetime.timedelta(days=1)
        Statistics().calculate_daily(last_date.strftime("%Y%m%d"))
    except Exception as e:
        logger.exception(e)


@app.task(soft_time_limit=59, time_limit=60)
def delete_expire_order(timeout):
    logger.info("执行定时任务 delete_expire_order")

    class GoodsOrder(object):
        pass

    class Goods(object):
        pass

    class StoreGoods(object):
        pass

    class CouponOrder(object):
        pass

    session = get_db_session(
        [
            {"db_table": "sdet_order", "model": GoodsOrder},
            {"db_table": "sdet_goods", "model": Goods},
            {"db_table": "sdet_store_goods", "model": StoreGoods},
            {"db_table": "ebt_coupon_order", "model": CouponOrder},
        ]
    )

    try:
        now = datetime.datetime.now()
        goods_packing_id = ["571983673114813", "571983568187916"]  # 商品包装ID列表
        date_start = (now + datetime.timedelta(days=-2)).replace(
            hour=0, minute=0, second=0, microsecond=0
        )
        date_end = (now + datetime.timedelta(days=-1)).replace(
            hour=23, minute=59, second=59, microsecond=999999
        )
        filters = dict(
            sdef_order_status=1,
            sdef_order_is_delete=0,
            sdef_order_pay_status=0,
        )
        order_list = (
            session.query(GoodsOrder)
            .filter_by(**filters)
            .filter(GoodsOrder.sdef_order_create_time >= date_start)
            .filter(GoodsOrder.sdef_order_create_time <= date_end)
            .all()
        )
        for order in order_list:
            order_id = order.sdef_order_id
            store_id = order.sdef_order_store_id
            order_type = order.sdef_order_type
            redis = redis_util.client
            if not redis.get("order_id_%s" % order_id):
                if order.sdef_order_goods:
                    order_goods = json.loads(order.sdef_order_goods)
                    for g in order_goods:
                        if store_id > 0:
                            filters = dict(
                                sdef_goods_id=g["id"],
                                sdef_goods_store_id=store_id,
                                sdef_goods_factory_uid=FACTORY_UID,
                            )
                            goods = (
                                session.query(StoreGoods).filter_by(**filters).first()
                            )
                        else:
                            goods = (
                                session.query(Goods)
                                .filter_by(sdef_goods_id=g["id"])
                                .first()
                            )

                        if goods:
                            if (
                                order_type == OrderType.PINQU
                                and not store_id
                                and g["id"] not in goods_packing_id
                            ):
                                continue
                            goods.sdef_goods_count += int(g["count"])
                    if order.sdef_order_coupon_fee > Decimal("0"):
                        filters = dict(
                            user_id=order.sdef_order_device_id,
                            order_id=order_id,
                            status=0,
                        )
                        ret = session.query(CouponOrder).filter_by(**filters).delete()
                        logger.info(
                            "delete coupon_order ret=%s, order_id=%s" % (ret, order_id)
                        )
                order.sdef_order_is_delete = 1
                order.sdef_order_status = 5
                order.sdef_order_delete_time = datetime.datetime.now()
        session.commit()
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()


@app.task(soft_time_limit=59, time_limit=60)
def express_notify(timeout):
    logger.info("执行定时任务 express_notify")

    class GoodsOrder(object):
        pass

    session = get_db_session(
        [
            {"db_table": "sdet_order", "model": GoodsOrder},
        ]
    )

    try:
        now = datetime.datetime.now()
        hour = now.hour
        datum_time = (now + datetime.timedelta(days=-5)).replace(
            hour=8, minute=0, second=0, microsecond=0
        )
        filters = dict(
            sdef_order_status=3,
            sdef_order_is_delete=0,
            sdef_order_pay_status=1,
        )
        orders = (
            session.query(GoodsOrder)
            .filter_by(**filters)
            .filter(GoodsOrder.sdef_order_create_time >= datum_time)
            .filter(
                GoodsOrder.sdef_order_type
                not in [
                    OrderType.MEMBER,
                    OrderType.MEAL_DEDUCTION,
                    OrderType.FREE_CARD,
                    OrderType.GIFT_CARD,
                ]
            )
            .all()
        )
        for o in orders:
            id = o.sdef_order_id
            db = mongodb_util.db
            result = db.express_info.find_one({"_id": id})
            if not result:
                if o.sdef_order_express_no:
                    order_express_no = json.loads(o.sdef_order_express_no)
                    if len(order_express_no) > 0:
                        express_no_item = order_express_no[0]
                        if isinstance(express_no_item, dict):
                            express_number = express_no_item.get("express_number")
                            express_name = express_no_item.get("express_name")
                            data = {"_id": id, "is_send_notice": 1}
                            db.express_info.insert(data)
                            if o.sdef_order_device_id:
                                touser = o.sdef_order_device_id
                                goods_shipped_notice(
                                    id, express_name, express_number, touser
                                )
                elif o.sdef_order_status == OrderStatus.WAIT_PICKUP:
                    # 待提货订单每4小时通知供应商发货 9：00AM - 6:00PM
                    if 9 < hour < 18:
                        expedite_order_notice(id)
                        pass
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()


@app.task(soft_time_limit=59, time_limit=60)
def check_calculate_benefit(timeout):
    """
    定时检查近两天订单是否遗漏计算收益

    >>> check_calculate_benefit(30)

    :param timeout:
    :return:
    """
    logger.info("执行定时任务 check_calculate_benefit")
    try:

        class Order(object):
            pass

        class Agency(object):
            pass

        class UserAgency(object):
            pass

        class Benefit(object):
            pass

        session = get_db_session(
            [
                {"db_table": "sdet_order", "model": Order},
                {"db_table": "ebt_agency", "model": Agency},
                {"db_table": "ebt_user_agency", "model": UserAgency},
                {"db_table": "sdet_benefit", "model": Benefit},
            ]
        )
        now = datetime.datetime.now()
        date_start = (now + datetime.timedelta(days=-2)).replace(
            hour=0, minute=0, second=0, microsecond=0
        )
        date_end = now
        filters = dict(
            sdef_order_is_delete=0,
            sdef_order_pay_status=1,
        )
        orders = (
            session.query(Order)
            .filter_by(**filters)
            .filter(
                and_(
                    Order.sdef_order_type != OrderType.MEMBER,
                    Order.sdef_order_type != OrderType.TIYAN,
                )
            )
            .filter(Order.sdef_order_create_time >= date_start)
            .filter(Order.sdef_order_create_time <= date_end)
            .all()
        )

        for o in orders:
            order_status = o.sdef_order_status
            pay_time = o.sdef_order_pay_time
            minus_time = (datetime.datetime.now() - pay_time).total_seconds() / 60
            # 刚付款20分钟内的订单放到下次再检查
            if minus_time < float(20):
                continue
            # 失效或已退款订单无需计算提成
            if (
                order_status == OrderStatus.INVALID
                or order_status == OrderStatus.REFUND
            ):
                continue
            # 门店订单超过10个小时才检查，因为八个小时后才能更新预估收益为完成
            if o.sdef_order_store_id > 0 and minus_time < float(60 * 10):
                continue
            if order_status == OrderStatus.CHECK:
                continue
            order_status_list = [
                OrderStatus.WAIT_EXPRESS,
                OrderStatus.WAIT_PICKUP,
                OrderStatus.EXPRESSED,
                OrderStatus.RECEIVED,
            ]
            if (
                o.sdef_order_store_id > 0
                and o.sdef_order_ship_type > 0
                and order_status in order_status_list
            ):
                if o.sdef_order_type == 7 or o.sdef_order_pay_type == PayType.COUPON:
                    o.sdef_order_status = OrderStatus.COMPLETE
                    continue
                elif (
                    o.sdef_order_pay_type == PayType.BALANCE
                    and not IS_CALCULATE_BALANCE_BENEFIT
                ):
                    o.sdef_order_status = OrderStatus.COMPLETE
                    continue
                else:
                    o.sdef_order_status = OrderStatus.RECEIVED
            session.commit()

            try:
                # 统一调用计算预估收益
                logger.info(f"检查订单收益:order_id={o.sdef_order_id}")
                calculate_pre_benefit(o.sdef_order_id)
                # 更新预估收益为完成
                if o.sdef_order_status in [OrderStatus.RECEIVED, OrderStatus.COMPLETE]:
                    ret = pay_util.calculate_benefit(o.sdef_order_id)
                    logger.info(f"更新预估收益为完成：order_id={o.sdef_order_id}, result={ret}")
            except Exception as e:
                logger.exception(e)
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()


@app.task(soft_time_limit=899, time_limit=900)
def partner_daily_benefit(timeout):
    """
    合伙人每日中午12:00充值6元, 充值天数: 365×2
    :param timeout:
    :return:
    """
    logger.info("执行定时任务 partner_daily_benefit")
    redis = redis_util.client
    flag = redis.get("schedule_partner_daily_benefit")
    if flag:
        return
    redis.setex("schedule_partner_daily_benefit", 80000, 1)
    try:
        now = datetime.datetime.now()
        execute_date = datetime.datetime(2018, 7, 23, 0, 0, 0)  # 开始执行日期, 以实际为准

        class Order(object):
            pass

        class Agency(object):
            pass

        class UserAgency(object):
            pass

        class Benefit(object):
            pass

        class User(object):
            pass

        session = get_db_session(
            [
                {"db_table": "sdet_order", "model": Order},
                {"db_table": "ebt_agency", "model": Agency},
                {"db_table": "ebt_user_agency", "model": UserAgency},
                {"db_table": "sdet_benefit", "model": Benefit},
                {"db_table": "ebt_user", "model": User},
            ]
        )

        date_end = (now + datetime.timedelta(days=-1)).replace(
            hour=23, minute=59, second=59, microsecond=999999
        )
        filters = dict(
            sdef_order_is_delete=0,
            sdef_order_pay_status=1,
            sdef_order_status=4,
            sdef_order_type=3,
        )
        # 查询从活动开始到昨天结束时为止所有已付款的加盟费订单
        orders = (
            session.query(Order)
            .filter_by(**filters)
            .filter(Order.sdef_order_pay_time >= execute_date)
            .filter(Order.sdef_order_pay_time <= date_end)
            .all()
        )

        days = 365 * 2  # 充值天数
        for o in orders:
            if o.sdef_order_message == "1":
                continue
            open_id = o.sdef_order_device_id
            order_id = o.sdef_order_id
            order_fee = o.sdef_order_fee
            if order_fee == Decimal("199") or order_fee == Decimal("980"):
                if not o.sdef_order_message:
                    o.sdef_order_message = "1"
                continue

            if o.sdef_order_describe == "underline":
                if not o.sdef_order_message:
                    o.sdef_order_message = "1"
                continue

            if order_fee < Decimal("1999"):
                logger.error("加盟费订单金额错误: order_id=%s, oid=%s" % (order_id, open_id))
                continue

            if not open_id:
                logger.error("oid为空: order_id=%s" % order_id)
                continue

            user = session.query(User).filter_by(ebf_user_id=open_id).first()
            ua = (
                session.query(UserAgency).filter_by(ebf_ua_bind_user_id=open_id).first()
            )
            if not ua:
                logger.error(
                    "user_agency表无合伙人记录: order_id=%s, open_id=%s" % (order_id, open_id)
                )
                continue
            agency = (
                session.query(Agency).filter_by(ebf_agency_id=ua.ebf_agency_id).first()
            )
            agency_uid = user.ebf_user_agency_uid
            if agency_uid != agency.ebf_agency_uid:
                agency_uid = agency.ebf_agency_uid
                logger.error(
                    "合伙人普通账号没有绑定在自己名下: order_id=%s, open_id=%s" % (order_id, open_id)
                )
                user.ebf_user_agency_uid = agency_uid

            if not agency_uid:
                logger.error("agency_uid为空: order_id=%s, oid=%s" % (order_id, open_id))
                continue

            pay_time = o.sdef_order_pay_time
            base_time = datetime.datetime(2018, 11, 24, 10, 0, 0)
            new_base_time = datetime.datetime(2018, 11, 27, 18, 30, 0)
            if pay_time > base_time:
                money = Decimal("5")
                if pay_time > new_base_time:
                    count = order_fee / Decimal("2499")
                else:
                    count = order_fee / Decimal("2500")
                recommend_daily_benefit(order_id, count, order_fee, open_id)
            else:
                money = Decimal("6")
                remainder = int(order_fee) % 1999
                if remainder:
                    count = order_fee / Decimal("2300")
                else:
                    count = order_fee / Decimal("1999")
            fee = money * count
            _filter = dict(order_id=order_id, agency_uid=agency_uid, type=3, fee=fee)
            benefit_times = (
                session.query(func.count(Benefit.id)).filter_by(**_filter).scalar()
            )
            total_benefit = (
                session.query(func.sum(Benefit.fee)).filter_by(**_filter).scalar()
            )
            if not total_benefit:
                total_benefit = Decimal("0")
            total_fee = days * fee
            year = now.year
            month = now.month
            day = now.day
            start_time = datetime.datetime(year, month, day, 0, 0, 0)
            end_time = datetime.datetime(year, month, day, 23, 59, 59)
            old_bf = (
                session.query(Benefit)
                .filter_by(**_filter)
                .filter(Benefit.create_time >= start_time)
                .filter(Benefit.create_time <= end_time)
                .first()
            )
            if old_bf:
                logger.error("今日收益已返还:order_id=%s, oid=%s" % (order_id, open_id))
                continue
            if benefit_times >= days or total_benefit >= total_fee:
                o.sdef_order_message = "1"
                logger.info("收益充值次数已满:order_id=%s, oid=%s" % (order_id, open_id))
                continue
            first = "您的托管经营今日分红已到账！"
            daily_benefit_handle(order_id, agency_uid, fee, order_fee, open_id, first)
        session.commit()
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()


@app.task(soft_time_limit=899, time_limit=900)
def check_daily_benefit(timeout):
    """
    每天14:00, 16:00检查众筹收益返还是否遗漏
    :param timeout:
    :return:
    """
    logger.info("执行定时任务 check_daily_benefit")
    day_now = time.localtime()
    hour = day_now.tm_hour
    if hour < 13:
        return
    try:
        now = datetime.datetime.now()
        execute_date = datetime.datetime(2018, 7, 23, 0, 0, 0)  # 活动开始时间

        class Order(object):
            pass

        class Agency(object):
            pass

        class UserAgency(object):
            pass

        class Benefit(object):
            pass

        class User(object):
            pass

        session = get_db_session(
            [
                {"db_table": "sdet_order", "model": Order},
                {"db_table": "ebt_agency", "model": Agency},
                {"db_table": "ebt_user_agency", "model": UserAgency},
                {"db_table": "sdet_benefit", "model": Benefit},
                {"db_table": "ebt_user", "model": User},
            ]
        )

        date_end = (now + datetime.timedelta(days=-1)).replace(
            hour=23, minute=59, second=59, microsecond=999999
        )
        filters = dict(
            sdef_order_is_delete=0,
            sdef_order_pay_status=1,
            sdef_order_status=4,
            sdef_order_type=3,
        )
        # 查询从活动开始到昨天结束时为止所有已付款的加盟费订单
        orders = (
            session.query(Order)
            .filter_by(**filters)
            .filter(Order.sdef_order_pay_time >= execute_date)
            .filter(Order.sdef_order_pay_time <= date_end)
            .all()
        )

        days = 365 * 2  # 充值天数
        sign = False  # 是否遗漏标志
        for o in orders:
            if o.sdef_order_message == "1":
                continue
            open_id = o.sdef_order_device_id
            order_id = o.sdef_order_id
            order_fee = o.sdef_order_fee
            if order_fee == Decimal("199") or order_fee == Decimal("980"):
                if not o.sdef_order_message:
                    o.sdef_order_message = "1"
                continue

            if o.sdef_order_describe == "underline":
                if not o.sdef_order_message:
                    o.sdef_order_message = "1"
                continue

            if order_fee < Decimal("1999"):
                logger.error("加盟费订单金额错误: order_id=%s, oid=%s" % (order_id, open_id))
                continue

            if not open_id:
                logger.error("oid为空: order_id=%s" % order_id)
                continue

            user = session.query(User).filter_by(ebf_user_id=open_id).first()
            ua = (
                session.query(UserAgency).filter_by(ebf_ua_bind_user_id=open_id).first()
            )
            if not ua:
                logger.error(
                    "user_agency表无合伙人记录: order_id=%s, open_id=%s" % (order_id, open_id)
                )
                continue
            agency = (
                session.query(Agency).filter_by(ebf_agency_id=ua.ebf_agency_id).first()
            )
            agency_uid = user.ebf_user_agency_uid
            if agency_uid != agency.ebf_agency_uid:
                agency_uid = agency.ebf_agency_uid
                logger.error(
                    "合伙人普通账号没有绑定在自己名下: order_id=%s, open_id=%s" % (order_id, open_id)
                )
                user.ebf_user_agency_uid = agency_uid

            if not agency_uid:
                logger.error("agency_uid为空: order_id=%s, oid=%s" % (order_id, open_id))
                continue

            pay_time = o.sdef_order_pay_time
            base_time = datetime.datetime(2018, 11, 24, 10, 0, 0)
            new_base_time = datetime.datetime(2018, 11, 27, 18, 30, 0)
            if pay_time > base_time:
                money = Decimal("5")
                if pay_time > new_base_time:
                    count = order_fee / Decimal("2499")
                else:
                    count = order_fee / Decimal("2500")
            else:
                money = Decimal("6")
                remainder = int(order_fee) % 1999
                if remainder:
                    count = order_fee / Decimal("2300")
                else:
                    count = order_fee / Decimal("1999")
            fee = money * count
            _filter = dict(order_id=order_id, agency_uid=agency_uid, type=3, fee=fee)
            benefit_times = (
                session.query(func.count(Benefit.id)).filter_by(**_filter).scalar()
            )
            total_benefit = (
                session.query(func.sum(Benefit.fee)).filter_by(**_filter).scalar()
            )
            if not total_benefit:
                total_benefit = Decimal("0")
            total_fee = days * fee
            year = now.year
            month = now.month
            day = now.day
            start_time = datetime.datetime(year, month, day, 0, 0, 0)
            end_time = datetime.datetime(year, month, day, 23, 59, 59)
            old_bf = (
                session.query(Benefit)
                .filter_by(**_filter)
                .filter(Benefit.create_time >= start_time)
                .filter(Benefit.create_time <= end_time)
                .first()
            )
            if benefit_times >= days or total_benefit >= total_fee:
                o.sdef_order_message = "1"
                logger.info("收益充值次数已满:order_id=%s, oid=%s" % (order_id, open_id))
                continue
            if not old_bf:
                if not sign:
                    sign = True
                logger.info(
                    "遗漏的众筹订单 check_daily_benefit:order_id=%s, oid=%s"
                    % (order_id, open_id)
                )
                first = "您的托管经营今日分红已到账！"
                daily_benefit_handle(
                    order_id, agency_uid, fee, order_fee, open_id, first
                )
        if not sign:
            logger.info("check_daily_benefit: 没有遗漏众筹订单收益未返还")
        session.commit()
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()


@app.task(soft_time_limit=899, time_limit=900)
def agency_auto_upgrade(timeout):
    """
    合伙人自动升级
    :param timeout:
    :return:
    """
    logger.info("执行定时任务 agency_auto_upgrade")
    try:

        class Agency(object):
            pass

        session = get_db_session([{"db_table": "ebt_agency", "model": Agency}])
        agencies = (
            session.query(Agency)
            .filter_by(ebf_agency_type=AgencyType.PROFESSIONAL)
            .all()
        )
        for agency in agencies:
            agency_id = agency.ebf_agency_id
            agency_type = agency.ebf_agency_type
            agency_sub_type = agency.ebf_agency_sub_type
            parent_agency_id = agency.ebf_agency_parent_id
            filter = dict(
                ebf_agency_parent_id=agency_id, ebf_agency_type=AgencyType.PROFESSIONAL
            )
            # 名下直属高级酒庄代表人数
            direct_nums = (
                session.query(func.count(Agency.ebf_agency_id))
                .filter_by(**filter)
                .scalar()
            )
            num_list = []

            if direct_nums < 5:
                continue

            if direct_nums >= 20:
                logger.info(
                    "合伙人自动升级: 准备升级为事业部：agency_id=%s, direct_nums=%s"
                    % (agency_id, direct_nums)
                )
            else:
                total_list = get_agency_under_nums(num_list, agency_id)
                logger.info("num_list=%s, agency_id=%s" % (total_list, agency_id))
                total_nums = sum(total_list)

                if total_nums < 20:
                    continue

                logger.info(
                    "合伙人自动升级: 准备升级为事业部：agency_id=%s, total_nums=%s"
                    % (agency_id, total_nums)
                )

            # 如果父级合伙人不是事业合伙人，设置最近事业合伙人为父级合伙人
            if parent_agency_id:
                if parent_agency_id < 2:
                    uid = agency.ebf_agency_uid
                    career_agency = UserHelper.get_career_agency(uid)
                    if career_agency:
                        career_id = career_agency.ebf_agency_id
                        agency.ebf_agency_parent_id = career_id
                        msg = "合伙人自动升级: 更新父级: agency_id={}, parent_id={}, career_id={}"
                        logger.info(msg.format(agency_id, parent_agency_id, career_id))
                    else:
                        agency.ebf_agency_parent_id = DEFAULT_AGENCY_ID
                        logger.info(
                            "合伙人自动升级: 没有最近事业合伙人: parent_id=%s, agency_id=%s"
                            % (parent_agency_id, agency_id)
                        )
            else:
                agency.ebf_agency_parent_id = DEFAULT_AGENCY_ID
                logger.info(
                    "合伙人自动升级: 没有父级合伙人,设父级合伙人为默认: parent_id=%s, agency_id=%s"
                    % (parent_agency_id, agency_id)
                )

            if agency_type < AgencyType.CAREER:
                agency.ebf_agency_type = AgencyType.CAREER  # 合伙人升级为事业部
                session.commit()
                logger.info(f"合伙人自动升级: 合伙人升级为事业部: agency_id={agency_id}")
            elif agency_type == AgencyType.STORE and agency_sub_type == 1:
                agency.ebf_agency_sub_type = 3  # 门店升级为事业部
                logger.info(f"合伙人自动升级: 门店升级为事业部: agency_id={agency_id}")
                session.commit()
        session.close()
    except Exception as e:
        session.close()
        logger.exception(e)


@app.task(soft_time_limit=899, time_limit=900)
def handle_coupons(timeout):
    """
    处理优惠券生效以及过期
    :param timeout:
    :return:
    """
    logger.info("scheduler定时任务: handle_coupons")
    try:

        class CouponUser(object):
            pass

        class Coupon(object):
            pass

        class Order(object):
            pass

        session = get_db_session(
            [
                {"db_table": "ebt_coupon_user", "model": CouponUser},
                {"db_table": "ebt_coupon", "model": Coupon},
                {"db_table": "sdet_order", "model": Order},
            ]
        )

        filters = dict(sdef_order_pay_status=1, sdef_order_is_delete=0)
        cus = session.query(CouponUser).filter_by(status=0).all()
        base_hour = 6

        for cu in cus:
            now = datetime.datetime.now()
            order_id = cu.order_id
            is_active = cu.is_active
            cp_id = cu.coupon_id
            expiry_date = cu.expiry_date
            cu_id = cu.id
            create_time = cu.create_time
            minus_time = (now - create_time).total_seconds() / 3600  # 计算优惠券领取到现在多少个小时
            filters["sdef_order_id"] = order_id
            o = session.query(Order).filter_by(**filters).first()
            if order_id == "00000000000000000" and expiry_date:
                if expiry_date <= now:
                    cu.status = -1
                    continue
                else:
                    continue

            if not o:
                cu.status = 2
                continue

            store_id = o.sdef_order_store_id
            order_status = o.sdef_order_status
            pay_time = o.sdef_order_pay_time.strftime("%Y-%m-%d %H:%M:%S")
            cp = session.query(Coupon).filter_by(id=cp_id).first()
            days = cp.effective_days

            if is_active:
                if expiry_date <= now:
                    cu.status = -1
                    logger.info(
                        f"handle_coupons: coupon_user_id={cu_id}, order_id={order_id}, pay_time={pay_time}"
                    )
                    continue
                else:
                    continue
            else:
                if (minus_time >= base_hour and store_id) or (
                    not store_id and order_status in [4, 32]
                ):
                    cu.is_active = 1
                    cu.effect_date = now
                    cu.expiry_date = (now + datetime.timedelta(days=days)).replace(
                        hour=23, minute=59, second=58
                    )
                    if store_id and order_status != 4:
                        o.sdef_order_status = 4
                        BenefitHelper.create_complete_order(order_id)

                    logger.info(
                        f"handle_coupons: coupon_user_id={cu_id}, order_id={order_id}, pay_time={pay_time}"
                    )
                    continue
                else:
                    continue

        session.commit()
        session.close()
    except Exception as e:
        session.close()
        logger.exception(e)


@app.task(soft_time_limit=899, time_limit=900)
def check_tiyan_order(timeout):
    """
    检查体验订单是否过期
    :param timeout:
    :return:
    """
    logger.info("scheduler定时任务: check_tiyan_order")
    try:

        class User(object):
            pass

        class Order(object):
            pass

        session = get_db_session(
            [
                {"db_table": "ebt_user", "model": User},
                {"db_table": "sdet_order", "model": Order},
            ]
        )

        filters = dict(
            sdef_order_status=OrderStatus.WAIT_VALID,
            sdef_order_is_delete=0,
            sdef_order_pay_status=PayStatus.SUCCESS,
            sdef_order_type=OrderType.TIYAN,
        )

        orders = session.query(Order).filter_by(**filters).all()
        for o in orders:
            order_id = o.sdef_order_id
            user_id = o.sdef_order_device_id
            pay_time = o.sdef_order_pay_time
            expiry_time = pay_time + datetime.timedelta(days=3)
            now = datetime.datetime.now()
            user = session.query(User).filter_by(ebf_user_id=user_id).first()
            recommend_order_number = user.ebf_user_recommend_order_number
            if now >= expiry_time and recommend_order_number < TI_YAN_QUOTA:
                # 体验订单失败发放99拼柜优惠券
                result = CouponHandler.create_coupon(user_id, 6, order_id=order_id)
                if not result:
                    logger.error(f"check_tiyan_order: 99拼柜券创建失败 order_id={order_id}")
                    continue

                # 体验订单体验失败修改订单状态为完成
                o.sdef_order_status = OrderStatus.COMPLETE
                o.sdef_order_remarks = f"规定时间内未达到推荐体验订单数目:{recommend_order_number}"

                # 用户推荐体验订单数目清零
                user.ebf_user_recommend_order_number = 0
                logger.info(
                    f"check_tiyan_order: order_id={order_id} recommend_order_number={recommend_order_number}"
                )

                # 发送通知
                total_amount = Decimal("9.90")
                store_id = 1
                order_type = OrderType.TIYAN

                try:
                    first = f"您的体验订单已失效，根据活动规则转发拼柜购酒券"
                    remark = f"三天内未邀满九人下单，您的订单无法生效，9.9元转为99元拼柜购酒券，请尽快使用。拼柜批发省好多！\n点击详情使用拼柜购酒券"
                    tasks.send_order_pay_success_notice.delay(
                        user_id,
                        order_id,
                        total_amount,
                        store_id,
                        order_type,
                        first,
                        remark,
                    )
                    logger.info(f"体验: 发送体验拼柜券通知成功 order_id={order_id} user={user_id}")
                except Exception as e:
                    logger.exception(e)
                    logger.error(f"体验: 发送体验拼柜券通知失败 order_id={order_id} user={user_id}")

        session.commit()
        session.close()
    except Exception as e:
        session.close()
        logger.exception(e)


@app.task(soft_time_limit=59, time_limit=60)
def gift_order_refund(timeout):
    logger.info("执行定时任务 gift_order_refund")

    class GoodsOrder(object):
        pass

    class GiftInfo(object):
        pass

    session = get_db_session(
        [
            {"db_table": "sdet_order", "model": GoodsOrder},
            {"db_table": "ebt_gift_info", "model": GiftInfo},
        ]
    )

    try:
        now = datetime.datetime.now()
        datum_time = now + datetime.timedelta(seconds=-GIFT_ORDER_REFUND_TIMEOUT)
        filters = dict(
            sdef_order_type=OrderType.GIFT_CARD,
            sdef_order_status=OrderStatus.WAIT_EXPRESS,
            sdef_order_is_delete=0,
            sdef_order_pay_status=1,
        )
        orders = (
            session.query(GoodsOrder)
            .filter_by(**filters)
            .filter(GoodsOrder.sdef_order_pay_time < datum_time)
            .all()
        )
        for order in orders:
            # 礼品订单/送礼订单（礼品卡订单）自动退款
            order_id = order.sdef_order_id
            order_type = order.sdef_order_type

            # 检查送礼订单是否被领取
            if order_type == OrderType.GIFT_CARD:
                gift = (
                    session.query(GiftInfo)
                    .filter_by(gift_sender_order_id=order_id)
                    .first()
                )
                if not gift:
                    logger.error(f"送礼订单没有礼品信息记录：order_id={order_id}")
                    session.close()
                    continue
                if gift.status != 1:
                    logger.error(
                        f"送礼订单状态不是待领取：order_id={order_id} gift_status={gift.status}"
                    )
                    session.close()
                    continue

            # 检查支付方式
            if order.sdef_order_pay_type == PayType.BALANCE:
                refund_instance = BalanceRefund(order_id)
            elif order.sdef_order_pay_type == PayType.WECHAT:
                refund_instance = WechatRefund(order_id)
            # 订单退款
            if order_refund(refund_instance):
                logger.info(f"gift_order_refund: 订单退款成功 order_id={order_id}")
                order.sdef_order_status = OrderStatus.REFUND
                session.commit()
                # 订单退款通知
                touser = order.sdef_order_device_id
                total_amount = order.sdef_order_balance_fee + order.sdef_order_fee
                goods = ""
                cart_goods = json.loads(order.sdef_order_goods)
                for g in cart_goods:
                    goods += f"{g.goods_name} x {g.count}\n"
                address = f""
                reason = f"送礼订单超时自动退款"
                first = f"您好，您的礼品订单因为超过24小时没被领取，系统自动取消订单并退款"
                remark = f"系统自动原路退款，如果1小时后没有收到，请联系客服"
                url = f"https://{DOMAIN}/order/{order_id}"
                cancel_order_notice(
                    touser,
                    order_id,
                    total_amount,
                    goods,
                    address,
                    reason,
                    first,
                    remark,
                    url,
                )
            else:
                logger.error(f"gift_order_refund: 订单退款失败 order_id={order_id}")
            continue
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()


@app.task(soft_time_limit=59, time_limit=60)
def check_order(timeout):
    logger.info("执行定时任务 check_order")

    class GoodsOrder(object):
        pass

    session = get_db_session(
        [
            {"db_table": "sdet_order", "model": GoodsOrder},
        ]
    )

    try:
        now = datetime.datetime.now()
        datum_time = now + datetime.timedelta(seconds=-24*60*60)  # 24hr
        filters = dict(
            sdef_order_status=OrderStatus.WAIT_EXPRESS,
            sdef_order_is_delete=0,
            sdef_order_pay_status=1,
        )
        orders = (
            session.query(GoodsOrder)
            .filter_by(**filters)
            .filter(GoodsOrder.sdef_order_pay_time < datum_time)
            .all()
        )
        for order in orders:
            order_id = order.sdef_order_id
            logger.info(f"check_order: 订单出货超时 order_id={order_id}")
            expedite_order_notice(order_id)       
    except Exception as e:
        logger.exception(e)
    finally:
        session.close()
